/*
 * Copyright (c) 2011, Yawning <yawninglol at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   * Neither the name of the Mew Developers nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package yawning.mew.cat;

import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import yawning.mew.ProcEffect;
import yawning.mew.Report;

public class CatSimReport extends Report {
	public enum CatSimStat implements IStat {
		TOTAL_DAMAGE,

		DPS_NORMAL,		// DPS > 25% target HP
		DPS_BITW,		// DPS < 25% target HP

		FERAL_FF_UPTIME,
		HEROISM_UPTIME,
		UNHOLY_FRENZY_UPTIME,
		TOTT_UPTIME,
		SHATTERING_THROW_UPTIME,

		NR_SKULL_BASHES,
		NR_INTERRUPTED_CASTS,
		NR_TARGET_CASTS,

		ENERGY_GAINED,
		ENERGY_GAINED_PASSIVE,
		ENERGY_GAINED_KOTJ,
		ENERGY_GAINED_PRIMAL_MADNESS,
		PRIMAL_MADNESS_AVG_ENERGY_GAINED,
		ENERGY_LOST_SHIFTING,
		ENERGY_SPENT,
		ENERGY_OVERFLOW,
		OOC_PROCS,
		OOC_PROCS_CONSUMED,
		OOC_PROCS_WASTED,
		OOC_AVG_ENERGY_RETURN,
		OOC_EFFECTIVE_PPM,
		BERSERKS,
		BERSERK_AVG_DURATION,
		BERSERK_UPTIME,
		ENERGY_EFFECTIVE_REGEN_RATE,

		CP_GAINED,
		CP_GAINED_PRIMAL_FURY,
		CP_OVERFLOW,
		CP_SPENT,
		CP_GEN_RATE,

		TIGERS_FURYS,
		TIGERS_FURY_INTERVAL,
		TIGERS_FURY_UPTIME,

		P_WHITE_ATTACK_DMG,
		P_FURY_SWIPES_DMG,
		P_POUNCE_DMG,
		P_RAVAGE_DMG,
		P_CLAW_DMG,
		P_MANGLE_DMG,
		P_SHRED_DMG,
		P_RAKE_DMG,
		P_RIP_DMG,
		P_FEROCIOUS_BITE_DMG,
		P_MAIM_DMG,
		P_SWIPE_DMG,
		P_FIERY_CLAWS_DMG,
		P_OTHER_DMG,

		P_ENERGY_RAVAGE,
		P_ENERGY_POUNCE,
		P_ENERGY_CLAW,
		P_ENERGY_MANGLE,
		P_ENERGY_SHRED,
		P_ENERGY_RAKE,
		P_ENERGY_RIP,
		P_ENERGY_FEROCIOUS_BITE,
		P_ENERGY_MAIM,
		P_ENERGY_SAVAGE_ROAR,
		P_ENERGY_SWIPE,
		P_CP_RIP,
		P_CP_FEROCIOUS_BITE,
		P_CP_MAIM,
		P_CP_SAVAGE_ROAR,
		P_OOC_CLAW,
		P_OOC_MANGLE,
		P_OOC_SHRED,
		P_OOC_RAKE,
		P_OOC_RIP,
		P_OOC_FEROCIOUS_BITE,
		P_OOC_MAIM,
		P_OOC_SWIPE,

		POUNCE_DPE,
		RAVAGE_DPE,
		CLAW_DPE,
		MANGLE_DPE,
		SHRED_DPE,
		RAKE_DPE,
		RIP_DPE,
		FEROCIOUS_BITE_DPE,
		MAIM_DPE,
		SAVAGE_ROAR_DPE,
		SWIPE_DPE,

		WHITE_ATTACK_DPS,
		WHITE_ATTACKS,
		P_WHITE_ATTACK_MISS,
		P_WHITE_ATTACK_DODGE,
		P_WHITE_ATTACK_PARRY,
		P_WHITE_ATTACK_GLANCE,
		P_WHITE_ATTACK_CRIT,
		P_WHITE_ATTACK_HIT,
		WHITE_ATTACK_UPTIME,
		WHITE_ATTACK_SWING_TIMER,
		WHITE_ATTACK_AVG_DMG,

		SAVAGE_ROARS,
		SAVAGE_ROAR_AVG_CPS,
		SAVAGE_ROAR_UPTIME,

		FURY_SWIPES_DPS,
		FURY_SWIPES,
		P_FURY_SWIPES_MISS,
		P_FURY_SWIPES_DODGE,
		P_FURY_SWIPES_PARRY,
		P_FURY_SWIPES_CRIT,
		P_FURY_SWIPES_HIT,
		FURY_SWIPES_INTERVAL,
		FURY_SWIPES_AVG_DMG,

		POUNCE_DPS,
		POUNCES,
		P_POUNCE_MISS,
		P_POUNCE_DODGE,
		P_POUNCE_PARRY,
		P_POUNCE_HIT,
		POUNCE_TICKS,
		P_POUNCE_DOT_CRIT,
		P_POUNCE_DOT_HIT,
		POUNCE_DOT_AVG_DMG,
		POUNCE_UPTIME,
		P_POUNCE_TF,

		RAVAGE_DPS,
		RAVAGES,
		P_RAVAGE_MISS,
		P_RAVAGE_DODGE,
		P_RAVAGE_PARRY,
		P_RAVAGE_CRIT,
		P_RAVAGE_HIT,
		RAVAGE_AVG_DMG,
		P_RAVAGE_TF,
		FERAL_CHARGES,

		CLAW_DPS,
		CLAWS,
		P_CLAW_MISS,
		P_CLAW_DODGE,
		P_CLAW_PARRY,
		P_CLAW_CRIT,
		P_CLAW_HIT,
		CLAW_AVG_DMG,
		P_CLAW_TF,

		MANGLE_DPS,
		MANGLES,
		P_MANGLE_MISS,
		P_MANGLE_DODGE,
		P_MANGLE_PARRY,
		P_MANGLE_CRIT,
		P_MANGLE_HIT,
		MANGLE_UPTIME,
		MANGLE_AVG_DMG,
		P_MANGLE_TF,

		SHRED_DPS,
		SHREDS,
		P_SHRED_MISS,
		P_SHRED_DODGE,
		P_SHRED_PARRY,
		P_SHRED_CRIT,
		P_SHRED_HIT,
		SHRED_AVG_DMG,
		P_SHRED_TF,

		RAKE_DPS,
		RAKE_DIRECT_DPS,
		RAKES,
		P_RAKE_MISS,
		P_RAKE_DODGE,
		P_RAKE_PARRY,
		P_RAKE_CRIT,
		P_RAKE_HIT,
		RAKE_AVG_DMG,
		RAKE_DOT_DPS,
		RAKE_TICKS,
		P_RAKE_DOT_CRIT,
		P_RAKE_DOT_HIT,
		RAKE_DOT_AVG_DMG,
		RAKE_UPTIME,
		P_RAKE_TF,

		RIP_DPS,
		RIPS,
		RIPS_NORMAL,
		RIPS_BITW,
		P_RIP_MISS,
		P_RIP_DODGE,
		P_RIP_PARRY,
		P_RIP_HIT,
		RIP_TICKS,
		P_RIP_DOT_CRIT,
		P_RIP_DOT_HIT,
		RIP_DOT_AVG_DMG,
		RIP_UPTIME,
		P_RIP_TF,

		FEROCIOUS_BITE_DPS,
		FEROCIOUS_BITES,
		FEROCIOUS_BITES_NORMAL,
		FEROCIOUS_BITES_BITW,
		P_FEROCIOUS_BITE_MISS,
		P_FEROCIOUS_BITE_DODGE,
		P_FEROCIOUS_BITE_PARRY,
		P_FEROCIOUS_BITE_CRIT,
		P_FEROCIOUS_BITE_HIT,
		FEROCIOUS_BITE_AVG_EXTRA_ENERGY,
		FEROCIOUS_BITE_AVG_DMG,
		P_FEROCIOUS_BITE_TF,

		MAIM_DPS,
		MAIMS,
		P_MAIM_MISS,
		P_MAIM_DODGE,
		P_MAIM_PARRY,
		P_MAIM_CRIT,
		P_MAIM_HIT,
		MAIM_AVG_DMG,
		P_MAIM_TF,

		SWIPE_DPS,
		SWIPES,
		P_SWIPE_MISS,
		P_SWIPE_DODGE,
		P_SWIPE_PARRY,
		P_SWIPE_CRIT,
		P_SWIPE_HIT,
		SWIPE_AVG_DMG,
		P_SWIPE_TF,

		FIERY_CLAWS_DPS,
		FIERY_CLAWS_TICKS,
		FIERY_CLAWS_AVG_DMG,
		FIERY_CLAWS_ROLLED,
		FIERY_CLAWS_ROLLED_DMG,
		FIERY_CLAWS_MUNCHED,
		FIERY_CLAWS_MUNCHED_DMG,
		FIERY_CLAWS_REFRESH_LOST,
		FIERY_CLAWS_REFRESH_LOST_DMG,

		OTHER_DPS,
	}

	class CatSimTargetReport extends Report {
		boolean mHaveValidBitWDPS = true;
		boolean mShouldReportParry = false;

		@Override
		public Report accumulate(Report r) {
			super.accumulate(r);

			CatSimTargetReport br = (CatSimTargetReport) r;

			if (br.mHaveValidBitWDPS == false) mHaveValidBitWDPS = false;
			if (br.mShouldReportParry == true) mShouldReportParry = true;

			return this;
		}
	}

	boolean mHaveValidRavageDPE = true;
	Map<ProcEffect, Double> mProcUptimes;	// Proc uptimes
	Map<String, CatSimTargetReport> mPerTargetReports;	// Per target stats by target name
	List<String> mSortedTargetList;

	double[] mDamageLog;	// Individual logs (do not use)
	public List<double[]> mDamageLogs;		// Accumulated logs

	@Override
	public Report accumulate(Report r) {
		super.accumulate(r);

		CatSimReport br = (CatSimReport) r;

		if (mProcUptimes == null) {
			// XXX: Should I copy this?
			mProcUptimes = br.mProcUptimes;
		} else {
			if (br.mProcUptimes != null && br.mProcUptimes.isEmpty() == false) {
				for (Map.Entry<ProcEffect, Double> entry : br.mProcUptimes.entrySet()) {
					ProcEffect proc = entry.getKey();
					Double newUptime = entry.getValue();
					Double oldUptime = mProcUptimes.get(proc);

					mProcUptimes.put(proc, (oldUptime + newUptime));
				}
			}
		}

		// Accumulate per target stats
		if (mPerTargetReports == null) {
			mPerTargetReports = br.mPerTargetReports;
		} else {
			for (Map.Entry<String, CatSimTargetReport> entry: br.mPerTargetReports.entrySet()) {
				String targetName = entry.getKey();
				CatSimTargetReport targetStats = entry.getValue();

				CatSimTargetReport oldReport = mPerTargetReports.get(targetName);
				if (oldReport == null) {
					mPerTargetReports.put(targetName, targetStats);
				} else {
					oldReport.accumulate(targetStats);
				}
			}
		}

		if (br.mHaveValidRavageDPE == false) mHaveValidRavageDPE = false;

		// Accumulate the spawn ordered target list
		LinkedHashSet<String> newSortedTargetSet = new LinkedHashSet<String>();
		if (mSortedTargetList == null) {
			mSortedTargetList = br.mSortedTargetList;
		} else {
			int idx1 = 0, idx2 = 0;
			while (idx1 < mSortedTargetList.size() && idx2 < br.mSortedTargetList.size()) {
				int cmp = mSortedTargetList.get(idx1).compareTo(br.mSortedTargetList.get(idx2));
				if (cmp < 0) {
					newSortedTargetSet.add(mSortedTargetList.get(idx1++));
				} else if (cmp > 0) {
					newSortedTargetSet.add(br.mSortedTargetList.get(idx2++));
				} else if (cmp == 0) {
					newSortedTargetSet.add(mSortedTargetList.get(idx1++));
					idx2++;
				}
			}

			if (idx1 == mSortedTargetList.size()) {
				while(idx2 < br.mSortedTargetList.size())
					newSortedTargetSet.add(br.mSortedTargetList.get(idx2++));
			} else {
				while(idx1 < br.mSortedTargetList.size())
					newSortedTargetSet.add(mSortedTargetList.get(idx1++));
			}

			mSortedTargetList = new LinkedList<String>(newSortedTargetSet);
		}

		// Accumulate the damage log
		if (br.mDamageLog != null) {
			if (mDamageLogs == null) mDamageLogs = new LinkedList<double[]>();
			mDamageLogs.add(br.mDamageLog);
		}

		return this;
	}

	@Override
	public String toString() {
		StringBuilder tmp = new StringBuilder();

		int iterations = getIterations();
		boolean onlyOneTarget = mPerTargetReports.size() == 1;

		Result res = getResult(Stat.DAMAGE_DEALT_PER_SECOND);
		tmp.append("DPS: ").append(round(res.getMean())).append(" +/- ").append(round(res.getStandardError() * 1.96)).append('\n');
		tmp.append("Min/Max DPS: ").append(round(res.getMin())).append(" / ").append(round(res.getMax())).append('\n');
		tmp.append("StdDev: ").append(round(res.getStandardDeviation())).append('\n');
		tmp.append('\n');

		if (onlyOneTarget) {
			CatSimTargetReport tr = mPerTargetReports.get(mPerTargetReports.keySet().iterator().next());
			if (tr.mHaveValidBitWDPS) {
				tmp.append("DPS (non-BitW): ").append(round(tr.getResult(CatSimStat.DPS_NORMAL).getMean())).append('\n');
				tmp.append("DPS (BitW): ").append(round(tr.getResult(CatSimStat.DPS_BITW).getMean())).append('\n');
				tmp.append('\n');
			}
		}

		// Buff/Debuffs
		if (getResult(CatSimStat.HEROISM_UPTIME).getMax() > 0 || getResult(CatSimStat.TOTT_UPTIME).getMax() > 0 || getResult(CatSimStat.UNHOLY_FRENZY_UPTIME).getMax() > 0) {
			tmp.append("External Buffs:").append('\n');
			if (getResult(CatSimStat.HEROISM_UPTIME).getMax() > 0) {
				tmp.append(" Heroism uptime: ").append(round(getResult(CatSimStat.HEROISM_UPTIME).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.TOTT_UPTIME).getMax() > 0) {
				tmp.append(" Tricks of the Trade uptime: ").append(round(getResult(CatSimStat.TOTT_UPTIME).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.UNHOLY_FRENZY_UPTIME).getMax() > 0) {
				tmp.append(" Unholy Frenzy uptime: ").append(round(getResult(CatSimStat.UNHOLY_FRENZY_UPTIME).getMean())).append('%').append('\n');
			}
			tmp.append('\n');
		}

		// Energy/CP metrics
		tmp.append("Resource Stats:").append('\n');
		tmp.append(" Energy:").append('\n');
		tmp.append("  Energy gained: ").append(round(getResult(CatSimStat.ENERGY_GAINED).getMean())).append('\n');
		tmp.append("   Passive: ").append(round(getResult(CatSimStat.ENERGY_GAINED_PASSIVE).getMean())).append('\n');
		if (getResult(CatSimStat.ENERGY_GAINED_KOTJ).getMax() > 0) {
			tmp.append("   King of the Jungle: ").append(round(getResult(CatSimStat.ENERGY_GAINED_KOTJ).getMean())).append('\n');
		}
		if (getResult(CatSimStat.ENERGY_GAINED_PRIMAL_MADNESS).getMax() > 0) {
			tmp.append("   Primal Madness: ").append(round(getResult(CatSimStat.ENERGY_GAINED_PRIMAL_MADNESS).getMean())).append('\n');
			tmp.append("    Average energy gain: ").append(round(getResult(CatSimStat.PRIMAL_MADNESS_AVG_ENERGY_GAINED).getMean())).append('\n');
		}
		tmp.append("  Energy overflow: ").append(round(getResult(CatSimStat.ENERGY_OVERFLOW).getMean())).append('\n');
		tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.ENERGY_SPENT).getMean())).append('\n');
		if (getResult(CatSimStat.ENERGY_LOST_SHIFTING).getMax() > 0) {
			tmp.append("  Energy lost to shifts: ").append(round(getResult(CatSimStat.ENERGY_LOST_SHIFTING).getMean())).append('\n');
		}
		if (getResult(CatSimStat.OOC_PROCS).getMax() > 0) {
			tmp.append("  Omen of Clarity:").append('\n');
			tmp.append("   Nr. OoC procs: ").append(round(getResult(CatSimStat.OOC_PROCS).getMean())).append('\n');
			tmp.append("    Procs spent: ").append(round(getResult(CatSimStat.OOC_PROCS_CONSUMED).getMean())).append('\n');
			tmp.append("    Procs wasted: ").append(round(getResult(CatSimStat.OOC_PROCS_WASTED).getMean())).append('\n');
			tmp.append("   Effective PPM: ").append(round(getResult(CatSimStat.OOC_EFFECTIVE_PPM).getMean())).append('\n');
			tmp.append("   Average energy return: ").append(round(getResult(CatSimStat.OOC_AVG_ENERGY_RETURN).getMean())).append('\n');
		}
		if (getResult(CatSimStat.BERSERKS).getMax() > 0) {
			tmp.append("  Berserk:").append('\n');
			tmp.append("   Nr. Berserks: ").append(round(getResult(CatSimStat.BERSERKS).getMean())).append('\n');
			tmp.append("   Average duration: ").append(round(getResult(CatSimStat.BERSERK_AVG_DURATION).getMean())).append(" sec").append('\n');
			tmp.append("   Uptime: ").append(round(getResult(CatSimStat.BERSERK_UPTIME).getMean())).append('%').append('\n');
		}
		tmp.append("  Effective regen. rate: ").append(round(getResult(CatSimStat.ENERGY_EFFECTIVE_REGEN_RATE).getMean())).append(" e/s").append('\n');
		tmp.append(" Combo Points:").append('\n');
		tmp.append("  Combo Points gained: ").append(round(getResult(CatSimStat.CP_GAINED).getMean())).append('\n');
		tmp.append("   Primal Fury: ").append(round(getResult(CatSimStat.CP_GAINED_PRIMAL_FURY).getMean())).append('\n');
		tmp.append("  Combo Points overflow: ").append(round(getResult(CatSimStat.CP_OVERFLOW).getMean())).append('\n');
		tmp.append("  Combo Points spent: ").append(round(getResult(CatSimStat.CP_SPENT).getMean())).append('\n');
		tmp.append("  Gen. rate: ").append(round(getResult(CatSimStat.CP_GEN_RATE).getMean())).append(" cp/s").append('\n');
		tmp.append('\n');

		// Ability overview
		tmp.append("Ability Overview:").append('\n');
		if (getResult(CatSimStat.P_WHITE_ATTACK_DMG).getMax() > 0) {
			tmp.append(" White Attack damage: ").append(round(getResult(CatSimStat.P_WHITE_ATTACK_DMG).getMean())).append('%').append('\n');
			tmp.append("  Uptime: ").append(round(getResult(CatSimStat.WHITE_ATTACK_UPTIME).getMean())).append('%').append('\n');
			tmp.append("  Swing timer: ").append(round(getResult(CatSimStat.WHITE_ATTACK_SWING_TIMER).getMean())).append(" sec").append('\n');
		}
		if (getResult(CatSimStat.P_FURY_SWIPES_DMG).getMax() > 0) {
			tmp.append(" Fury Swipes damage: ").append(round(getResult(CatSimStat.P_FURY_SWIPES_DMG).getMean())).append('%').append('\n');
			tmp.append("  Interval: ").append(round(getResult(CatSimStat.FURY_SWIPES_INTERVAL).getMean())).append(" sec").append('\n');
		}
		if (getResult(CatSimStat.P_POUNCE_DMG).getMax() > 0) {
			tmp.append(" Pounce damage: ").append(round(getResult(CatSimStat.P_POUNCE_DMG).getMean())).append('%').append('\n');
			tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.P_ENERGY_POUNCE).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.POUNCE_DPE).getMax() > 0) {
				tmp.append("  DPE: ").append(round(getResult(CatSimStat.POUNCE_DPE).getMean())).append('\n');
			}
		}
		if (getResult(CatSimStat.P_RAVAGE_DMG).getMax() > 0) {
			tmp.append(" Ravage damage: ").append(round(getResult(CatSimStat.P_RAVAGE_DMG).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.P_ENERGY_RAVAGE).getMax() > 0) {
				tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.P_ENERGY_RAVAGE).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.RAVAGE_DPE).getMax() > 0 && mHaveValidRavageDPE) {
				tmp.append("  DPE: ").append(round(getResult(CatSimStat.RAVAGE_DPE).getMean())).append('\n');
			}
		}
		if (getResult(CatSimStat.P_CLAW_DMG).getMax() > 0) {
			tmp.append(" Claw damage: ").append(round(getResult(CatSimStat.P_CLAW_DMG).getMean())).append('%').append('\n');
			tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.P_ENERGY_CLAW).getMean())).append('%').append('\n');
			tmp.append("  OoC spent: ").append(round(getResult(CatSimStat.P_OOC_CLAW).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.CLAW_DPE).getMax() > 0) {
				tmp.append("  DPE: ").append(round(getResult(CatSimStat.CLAW_DPE).getMean())).append('\n');
			}
		}
		if (getResult(CatSimStat.P_MANGLE_DMG).getMax() > 0) {
			tmp.append(" Mangle damage: ").append(round(getResult(CatSimStat.P_MANGLE_DMG).getMean())).append('%').append('\n');
			tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.P_ENERGY_MANGLE).getMean())).append('%').append('\n');
			tmp.append("  OoC spent: ").append(round(getResult(CatSimStat.P_OOC_MANGLE).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.MANGLE_DPE).getMax() > 0) {
				tmp.append("  DPE: ").append(round(getResult(CatSimStat.MANGLE_DPE).getMean())).append('\n');
			}
		}
		if (getResult(CatSimStat.P_SHRED_DMG).getMax() > 0) {
			tmp.append(" Shred damage: ").append(round(getResult(CatSimStat.P_SHRED_DMG).getMean())).append('%').append('\n');
			tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.P_ENERGY_SHRED).getMean())).append('%').append('\n');
			tmp.append("  OoC spent: ").append(round(getResult(CatSimStat.P_OOC_SHRED).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.SHRED_DPE).getMax() > 0) {
				tmp.append("  DPE: ").append(round(getResult(CatSimStat.SHRED_DPE).getMean())).append('\n');
			}
		}
		if (getResult(CatSimStat.P_RAKE_DMG).getMax() > 0) {
			tmp.append(" Rake damage: ").append(round(getResult(CatSimStat.P_RAKE_DMG).getMean())).append('%').append('\n');
			tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.P_ENERGY_RAKE).getMean())).append('%').append('\n');
			tmp.append("  OoC spent: ").append(round(getResult(CatSimStat.P_OOC_RAKE).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.RAKE_DPE).getMax() > 0) {
				tmp.append("  DPE: ").append(round(getResult(CatSimStat.RAKE_DPE).getMean())).append('\n');
			}
		}
		if (getResult(CatSimStat.P_RIP_DMG).getMax() > 0) {
			tmp.append(" Rip damage: ").append(round(getResult(CatSimStat.P_RIP_DMG).getMean())).append('%').append('\n');
			tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.P_ENERGY_RIP).getMean())).append('%').append('\n');
			tmp.append("  OoC spent: ").append(round(getResult(CatSimStat.P_OOC_RIP).getMean())).append('%').append('\n');
			tmp.append("  CPs spent: ").append(round(getResult(CatSimStat.P_CP_RIP).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.RIP_DPE).getMax() > 0) {
				tmp.append("  DPE: ").append(round(getResult(CatSimStat.RIP_DPE).getMean())).append('\n');
			}
		}
		if (getResult(CatSimStat.P_FEROCIOUS_BITE_DMG).getMax() > 0) {
			tmp.append(" Ferocious Bite damage: ").append(round(getResult(CatSimStat.P_FEROCIOUS_BITE_DMG).getMean())).append('%').append('\n');
			tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.P_ENERGY_FEROCIOUS_BITE).getMean())).append('%').append('\n');
			tmp.append("  OoC spent: ").append(round(getResult(CatSimStat.P_OOC_FEROCIOUS_BITE).getMean())).append('%').append('\n');
			tmp.append("  CPs spent: ").append(round(getResult(CatSimStat.P_CP_FEROCIOUS_BITE).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.FEROCIOUS_BITE_DPE).getMax() > 0) {
				tmp.append("  DPE: ").append(round(getResult(CatSimStat.FEROCIOUS_BITE_DPE).getMean())).append('\n');
			}
		}
		if (getResult(CatSimStat.P_MAIM_DMG).getMax() > 0) {
			tmp.append(" Maim damage: ").append(round(getResult(CatSimStat.P_MAIM_DMG).getMean())).append('%').append('\n');
			tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.P_ENERGY_MAIM).getMean())).append('%').append('\n');
			tmp.append("  OoC spent: ").append(round(getResult(CatSimStat.P_OOC_MAIM).getMean())).append('%').append('\n');
			tmp.append("  CPs spent: ").append(round(getResult(CatSimStat.P_CP_MAIM).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.MAIM_DPE).getMax() > 0) {
				tmp.append("  DPE: ").append(round(getResult(CatSimStat.MAIM_DPE).getMean())).append('\n');
			}
		}
		if (getResult(CatSimStat.P_ENERGY_SAVAGE_ROAR).getMax() > 0) {
			tmp.append(" Savage Roar:").append('\n');
			tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.P_ENERGY_SAVAGE_ROAR).getMean())).append('%').append('\n');
			tmp.append("  CPs spent: ").append(round(getResult(CatSimStat.P_CP_SAVAGE_ROAR).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.SAVAGE_ROAR_DPE).getMax() > 0) {
				tmp.append("  DPE: ").append(round(getResult(CatSimStat.SAVAGE_ROAR_DPE).getMean())).append('\n');
			}
		}
		if (getResult(CatSimStat.P_SWIPE_DMG).getMax() > 0) {
			tmp.append(" Swipe damage: ").append(round(getResult(CatSimStat.P_SWIPE_DMG).getMean())).append('%').append('\n');
			tmp.append("  Energy spent: ").append(round(getResult(CatSimStat.P_ENERGY_SWIPE).getMean())).append('%').append('\n');
			tmp.append("  OoC spent: ").append(round(getResult(CatSimStat.P_OOC_SWIPE).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.SWIPE_DPE).getMax() > 0) {
				tmp.append("  DPE: ").append(round(getResult(CatSimStat.SWIPE_DPE).getMean())).append('\n');
			}
		}
		if (getResult(CatSimStat.P_FIERY_CLAWS_DMG).getMax() > 0) {
			tmp.append(" Fiery Claws damage: ").append(round(getResult(CatSimStat.P_FIERY_CLAWS_DMG).getMean())).append('%').append('\n');
		}
		if (getResult(CatSimStat.P_OTHER_DMG).getMax() > 0) {
			tmp.append(" Other damage: ").append(round(getResult(CatSimStat.P_OTHER_DMG).getMean())).append('%').append('\n');
		}
		tmp.append('\n');

		// Ability in-depth
		if (getResult(CatSimStat.TIGERS_FURYS).getMax() > 0) {
			tmp.append("Tiger's Fury:").append('\n');
			tmp.append(" Nr. Tiger's Furys: ").append(round(getResult(CatSimStat.TIGERS_FURYS).getMean())).append('\n');
			tmp.append(" Interval: ").append(round(getResult(CatSimStat.TIGERS_FURY_INTERVAL).getMean())).append(" sec").append('\n');
			tmp.append(" Uptime: ").append(round(getResult(CatSimStat.TIGERS_FURY_UPTIME).getMean())).append('%').append('\n');
			if (getResult(CatSimStat.POUNCES).getMax() > 0) {
				tmp.append(" Pounce coverage: ").append(round(getResult(CatSimStat.P_POUNCE_TF).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.RAVAGES).getMax() > 0) {
				tmp.append(" Ravage coverage: ").append(round(getResult(CatSimStat.P_RAVAGE_TF).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.CLAWS).getMax() > 0) {
				tmp.append(" Claw coverage: ").append(round(getResult(CatSimStat.P_CLAW_TF).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.MANGLES).getMax() > 0) {
				tmp.append(" Mangle coverage: ").append(round(getResult(CatSimStat.P_MANGLE_TF).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.SHREDS).getMax() > 0) {
				tmp.append(" Shred coverage: ").append(round(getResult(CatSimStat.P_SHRED_TF).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.RAKES).getMax() > 0) {
				tmp.append(" Rake coverage: ").append(round(getResult(CatSimStat.P_RAKE_TF).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.RIP_TICKS).getMax() > 0) {
				tmp.append(" Rip coverage: ").append(round(getResult(CatSimStat.P_RIP_TF).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.FEROCIOUS_BITES).getMax() > 0) {
				tmp.append(" Ferocious Bite coverage: ").append(round(getResult(CatSimStat.P_FEROCIOUS_BITE_TF).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.MAIMS).getMax() > 0) {
				tmp.append(" Maim coverage: ").append(round(getResult(CatSimStat.P_MAIM_TF).getMean())).append('%').append('\n');
			}
			if (getResult(CatSimStat.SWIPES).getMax() > 0) {
				tmp.append(" Swipe coverage: ").append(round(getResult(CatSimStat.P_SWIPE_TF).getMean())).append('%').append('\n');
			}
			tmp.append('\n');
		}

		// Feral Charge
		if (getResult(CatSimStat.FERAL_CHARGES).getMax() > 0) {
			tmp.append("Feral Charge:").append('\n');
			tmp.append(" Nr. Feral Charges: ").append(round(getResult(CatSimStat.FERAL_CHARGES).getMean())).append('\n');
			tmp.append('\n');
		}

		// Savage Roar
		if (getResult(CatSimStat.SAVAGE_ROARS).getMax() > 0) {
			tmp.append("Savage Roar:").append('\n');
			tmp.append(" Nr. Savage Roars: ").append(round(getResult(CatSimStat.SAVAGE_ROARS).getMean())).append('\n');
			tmp.append(" Average CP cost: ").append(round(getResult(CatSimStat.SAVAGE_ROAR_AVG_CPS).getMean())).append('\n');
			tmp.append(" Uptime: ").append(round(getResult(CatSimStat.SAVAGE_ROAR_UPTIME).getMean())).append('%').append('\n');
			tmp.append('\n');
		}

		// Per target report
		for (String targetName : mSortedTargetList) {
			CatSimTargetReport tr = mPerTargetReports.get(targetName);

			// Don't bother displaying spew for targets that we did nothing to.
			if (tr.getResult(CatSimStat.TOTAL_DAMAGE).getMax() == 0)
				continue;

			tmp.append("Target - ").append(targetName).append(':').append('\n');

			// DPS
			if (!onlyOneTarget) {
				tmp.append(" Total Damage: ").append(round(tr.getResult(CatSimStat.TOTAL_DAMAGE).getMean())).append('\n');
				Result tres = tr.getResult(Stat.DAMAGE_DEALT_PER_SECOND);
				tmp.append(" DPS: ").append(round(tres.getMean())).append(" +/- ").append(round(tres.getStandardError() * 1.96)).append('\n');
				tmp.append(" Min/Max DPS: ").append(round(tres.getMin())).append(" / ").append(round(tres.getMax())).append('\n');
				tmp.append(" StdDev: ").append(round(tres.getStandardDeviation())).append('\n');
				if (tres.getN() != iterations) {
					tmp.append(" Iterations Present: ").append(tres.getN()).append('\n');
				}
				// TODO: Active time?
				tmp.append('\n');

				if (tr.mHaveValidBitWDPS) {
					tmp.append(" DPS (non-BitW): ").append(round(tr.getResult(CatSimStat.DPS_NORMAL).getMean())).append('\n');
					tmp.append(" DPS (BitW): ").append(round(tr.getResult(CatSimStat.DPS_BITW).getMean())).append('\n');
					tmp.append('\n');
				}
			}

			// Target Debuffs
			tmp.append(" Target Debuffs:").append('\n');
			tmp.append("  Major Armor uptime: ").append(round(tr.getResult(CatSimStat.FERAL_FF_UPTIME).getMean())).append('%').append('\n');
			tmp.append("  Bleed Damage uptime: ").append(round(tr.getResult(CatSimStat.MANGLE_UPTIME).getMean())).append('%').append('\n');
			if (tr.getResult(CatSimStat.SHATTERING_THROW_UPTIME).getMax() > 0) {
				tmp.append("  Shattering Throw uptime: ").append(round(tr.getResult(CatSimStat.SHATTERING_THROW_UPTIME).getMean())).append('%').append('\n');
			}
			tmp.append('\n');

			// Interrupts
			if (tr.getResult(CatSimStat.NR_TARGET_CASTS).getMax() > 0) {
				tmp.append(" Interrupt Stats:").append('\n');
				tmp.append("  Interuptable casts: ").append(round(tr.getResult(CatSimStat.NR_TARGET_CASTS).getMean())).append('\n');
				tmp.append("  Nr. Skull Bashs: ").append(round(tr.getResult(CatSimStat.NR_SKULL_BASHES).getMean())).append('\n');
				tmp.append("  Interrupted casts: ").append(round(tr.getResult(CatSimStat.NR_INTERRUPTED_CASTS).getMean())).append('\n');
				tmp.append('\n');
			}

			// White damage
			if (tr.getResult(CatSimStat.WHITE_ATTACK_DPS).getMax() > 0) {
				tmp.append(" White Attack:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.WHITE_ATTACK_DPS).getMean())).append('\n');
				tmp.append("  Nr. White Attacks: ").append(round(tr.getResult(CatSimStat.WHITE_ATTACKS).getMean())).append('\n');
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Glance/Crit/Hit: ");
				else
					tmp.append("  Miss/Dodge/Glance/Crit/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_WHITE_ATTACK_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_WHITE_ATTACK_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_WHITE_ATTACK_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_WHITE_ATTACK_GLANCE).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_WHITE_ATTACK_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_WHITE_ATTACK_HIT).getMean())).append('%').append('\n');
				tmp.append("  Average damage: ").append(round(tr.getResult(CatSimStat.WHITE_ATTACK_AVG_DMG).getMean())).append('\n');
				tmp.append('\n');
			}

			// Fury Swipes
			if (tr.getResult(CatSimStat.FURY_SWIPES_DPS).getMax() > 0) {
				tmp.append(" Fury Swipes:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.FURY_SWIPES_DPS).getMean())).append('\n');
				tmp.append("  Nr. Fury Swipes: ").append(round(tr.getResult(CatSimStat.FURY_SWIPES).getMean())).append('\n');
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Crit/Hit: ");
				else
					tmp.append("  Miss/Dodge/Crit/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_FURY_SWIPES_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_FURY_SWIPES_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_FURY_SWIPES_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_FURY_SWIPES_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_FURY_SWIPES_HIT).getMean())).append('%').append('\n');
				tmp.append("  Average damage: ").append(round(tr.getResult(CatSimStat.FURY_SWIPES_AVG_DMG).getMean())).append('\n');
				tmp.append('\n');
			}

			// Pounce
			if (tr.getResult(CatSimStat.POUNCE_DPS).getMax() > 0) {
				tmp.append(" Pounce:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.POUNCE_DPS).getMean())).append('\n');
				tmp.append("  Nr. Pounces: ").append(round(tr.getResult(CatSimStat.POUNCES).getMean())).append('\n');
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Hit: ");
				else
					tmp.append("  Miss/Dodge/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_POUNCE_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_POUNCE_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_POUNCE_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_POUNCE_HIT).getMean())).append('%').append('\n');
				tmp.append("  DoT:").append('\n');
				tmp.append("   DPS: ").append(round(tr.getResult(CatSimStat.POUNCE_DPS).getMean())).append('\n');			// Redundant but who cares.
				tmp.append("   Nr. ticks: ").append(round(tr.getResult(CatSimStat.POUNCE_TICKS).getMean())).append('\n');
				tmp.append("   Crit/Hit: ").
					append(round(tr.getResult(CatSimStat.P_POUNCE_DOT_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_POUNCE_DOT_HIT).getMean())).append('%').append('\n');
				tmp.append("   Average damage: ").append(round(tr.getResult(CatSimStat.POUNCE_DOT_AVG_DMG).getMean())).append('\n');
				tmp.append("  Uptime: ").append(round(tr.getResult(CatSimStat.POUNCE_UPTIME).getMean())).append('%').append('\n');
				tmp.append('\n');
			}

			// Ravage
			if (tr.getResult(CatSimStat.RAVAGE_DPS).getMax() > 0) {
				tmp.append(" Ravage:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.RAVAGE_DPS).getMean())).append('\n');
				tmp.append("  Nr. Ravages: ").append(round(tr.getResult(CatSimStat.RAVAGES).getMean())).append('\n');
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Crit/Hit: ");
				else
					tmp.append("  Miss/Dodge/Crit/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_RAVAGE_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_RAVAGE_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_RAVAGE_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_RAVAGE_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_RAVAGE_HIT).getMean())).append('%').append('\n');
				tmp.append("  Average damage: ").append(round(tr.getResult(CatSimStat.RAVAGE_AVG_DMG).getMean())).append('\n');
				tmp.append('\n');
			}

			// Claw
			if (tr.getResult(CatSimStat.CLAW_DPS).getMax() > 0) {
				tmp.append(" Claw:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.CLAW_DPS).getMean())).append('\n');
				tmp.append("  Nr. Claws: ").append(round(tr.getResult(CatSimStat.CLAWS).getMean())).append('\n');
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Crit/Hit: ");
				else
					tmp.append("  Miss/Dodge/Crit/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_CLAW_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_CLAW_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_CLAW_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_CLAW_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_CLAW_HIT).getMean())).append('%').append('\n');
				tmp.append("  Average damage: ").append(round(tr.getResult(CatSimStat.CLAW_AVG_DMG).getMean())).append('\n');
				tmp.append('\n');
			}

			// Mangle
			if (tr.getResult(CatSimStat.MANGLE_DPS).getMax() > 0) {
				tmp.append(" Mangle:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.MANGLE_DPS).getMean())).append('\n');
				tmp.append("  Nr. Mangles: ").append(round(tr.getResult(CatSimStat.MANGLES).getMean())).append('\n');
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Crit/Hit: ");
				else
					tmp.append("  Miss/Dodge/Crit/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_MANGLE_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_MANGLE_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_MANGLE_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_MANGLE_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_MANGLE_HIT).getMean())).append('%').append('\n');
				tmp.append("  Uptime: ").append(round(tr.getResult(CatSimStat.MANGLE_UPTIME).getMean())).append('%').append('\n');
				tmp.append("  Average damage: ").append(round(tr.getResult(CatSimStat.MANGLE_AVG_DMG).getMean())).append('\n');
				tmp.append('\n');
			}

			// Shred
			if (tr.getResult(CatSimStat.SHRED_DPS).getMax() > 0) {
				tmp.append(" Shred:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.SHRED_DPS).getMean())).append('\n');
				tmp.append("  Nr. Shreds: ").append(round(tr.getResult(CatSimStat.SHREDS).getMean())).append('\n');
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Crit/Hit: ");
				else
					tmp.append("  Miss/Dodge/Crit/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_SHRED_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_SHRED_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_SHRED_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_SHRED_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_SHRED_HIT).getMean())).append('%').append('\n');
				tmp.append("  Average damage: ").append(round(tr.getResult(CatSimStat.SHRED_AVG_DMG).getMean())).append('\n');
				tmp.append('\n');
			}

			// Rake
			if (tr.getResult(CatSimStat.RAKE_DPS).getMax() > 0) {
				tmp.append(" Rake:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.RAKE_DPS).getMean())).append('\n');
				tmp.append("  Direct Damage:").append('\n');
				tmp.append("   DPS: ").append(round(tr.getResult(CatSimStat.RAKE_DIRECT_DPS).getMean())).append('\n');
				tmp.append("   Nr. Rakes: ").append(round(tr.getResult(CatSimStat.RAKES).getMean())).append('\n');
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Crit/Hit: ");
				else
					tmp.append("  Miss/Dodge/Crit/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_RAKE_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_RAKE_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_RAKE_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_RAKE_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_RAKE_HIT).getMean())).append('%').append('\n');
				tmp.append("   Average damage: ").append(round(tr.getResult(CatSimStat.RAKE_AVG_DMG).getMean())).append('\n');
				tmp.append("  DoT:").append('\n');
				tmp.append("   DPS: ").append(round(tr.getResult(CatSimStat.RAKE_DOT_DPS).getMean())).append('\n');
				tmp.append("   Nr. ticks: ").append(round(tr.getResult(CatSimStat.RAKE_TICKS).getMean())).append('\n');
				tmp.append("   Crit/Hit: ").
					append(round(tr.getResult(CatSimStat.P_RAKE_DOT_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_RAKE_DOT_HIT).getMean())).append('%').append('\n');
				tmp.append("   Average damage: ").append(round(tr.getResult(CatSimStat.RAKE_DOT_AVG_DMG).getMean())).append('\n');
				tmp.append("  Uptime: ").append(round(tr.getResult(CatSimStat.RAKE_UPTIME).getMean())).append('%').append('\n');
				tmp.append('\n');
			}

			// Rip
			if (tr.getResult(CatSimStat.RIP_DPS).getMax() > 0) {
				tmp.append(" Rip:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.RIP_DPS).getMean())).append('\n');
				tmp.append("  Nr. Rips: ").append(round(tr.getResult(CatSimStat.RIPS).getMean())).append('\n');
				if (tr.mHaveValidBitWDPS) {
					tmp.append("   Nr. Rips (non-BitW): ").append(round(tr.getResult(CatSimStat.RIPS_NORMAL).getMean())).append('\n');
					tmp.append("   Nr. Rips (BitW): ").append(round(tr.getResult(CatSimStat.RIPS_BITW).getMean())).append('\n');
				}
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Hit: ");
				else
					tmp.append("  Miss/Dodge/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_RIP_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_RIP_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_RIP_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_RIP_HIT).getMean())).append('%').append('\n');
				tmp.append("  DoT:").append('\n');
				tmp.append("   DPS: ").append(round(tr.getResult(CatSimStat.RIP_DPS).getMean())).append('\n');			// Redundant but who cares.
				tmp.append("   Nr. ticks: ").append(round(tr.getResult(CatSimStat.RIP_TICKS).getMean())).append('\n');
				tmp.append("   Crit/Hit: ").
					append(round(tr.getResult(CatSimStat.P_RIP_DOT_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_RIP_DOT_HIT).getMean())).append('%').append('\n');
				tmp.append("   Average damage: ").append(round(tr.getResult(CatSimStat.RIP_DOT_AVG_DMG).getMean())).append('\n');
				tmp.append("  Uptime: ").append(round(tr.getResult(CatSimStat.RIP_UPTIME).getMean())).append('%').append('\n');
				tmp.append('\n');
			}

			// Ferocious Bite
			if (tr.getResult(CatSimStat.FEROCIOUS_BITE_DPS).getMax() > 0) {
				tmp.append(" Ferocious Bite:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.FEROCIOUS_BITE_DPS).getMean())).append('\n');
				tmp.append("  Nr. Ferocious Bites: ").append(round(tr.getResult(CatSimStat.FEROCIOUS_BITES).getMean())).append('\n');
				if (tr.mHaveValidBitWDPS) {
					tmp.append("   Nr. Ferocious Bites (non-BitW): ").append(round(tr.getResult(CatSimStat.FEROCIOUS_BITES_NORMAL).getMean())).append('\n');
					tmp.append("   Nr. Ferocious Bites (BitW): ").append(round(tr.getResult(CatSimStat.FEROCIOUS_BITES_BITW).getMean())).append('\n');
				}
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Crit/Hit: ");
				else
					tmp.append("  Miss/Dodge/Crit/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_FEROCIOUS_BITE_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_FEROCIOUS_BITE_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_FEROCIOUS_BITE_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_FEROCIOUS_BITE_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_FEROCIOUS_BITE_HIT).getMean())).append('%').append('\n');
				tmp.append("  Average extra energy: ").append(round(tr.getResult(CatSimStat.FEROCIOUS_BITE_AVG_EXTRA_ENERGY).getMean())).append('\n');
				tmp.append("  Average damage: ").append(round(tr.getResult(CatSimStat.FEROCIOUS_BITE_AVG_DMG).getMean())).append('\n');
				tmp.append('\n');
			}

			// Maim
			if (tr.getResult(CatSimStat.MAIM_DPS).getMax() > 0) {
				tmp.append(" Maim:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.MAIM_DPS).getMean())).append('\n');
				tmp.append("  Nr. Maims: ").append(round(tr.getResult(CatSimStat.MAIMS).getMean())).append('\n');
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Crit/Hit: ");
				else
					tmp.append("  Miss/Dodge/Crit/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_MAIM_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_MAIM_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_MAIM_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_MAIM_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_MAIM_HIT).getMean())).append('%').append('\n');
				tmp.append("  Average damage: ").append(round(tr.getResult(CatSimStat.MAIM_AVG_DMG).getMean())).append('\n');
				tmp.append('\n');
			}

			// Swipe
			if (tr.getResult(CatSimStat.SWIPE_DPS).getMax() > 0) {
				tmp.append(" Swipe:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.SWIPE_DPS).getMean())).append('\n');
				tmp.append("  Nr. Swipes: ").append(round(tr.getResult(CatSimStat.SWIPES).getMean())).append('\n');
				if (tr.mShouldReportParry)
					tmp.append("  Miss/Dodge/Parry/Crit/Hit: ");
				else
					tmp.append("  Miss/Dodge/Crit/Hit: ");
				tmp.append(round(tr.getResult(CatSimStat.P_SWIPE_MISS).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_SWIPE_DODGE).getMean())).append('%').append('/');
				if (tr.mShouldReportParry) tmp.append(round(tr.getResult(CatSimStat.P_SWIPE_PARRY).getMean())).append('%').append('/');
				tmp.append(round(tr.getResult(CatSimStat.P_SWIPE_CRIT).getMean())).append('%').append('/').
					append(round(tr.getResult(CatSimStat.P_SWIPE_HIT).getMean())).append('%').append('\n');
				tmp.append("  Average damage: ").append(round(tr.getResult(CatSimStat.SWIPE_AVG_DMG).getMean())).append('\n');
				tmp.append('\n');
			}

			// Fiery Claws
			if (tr.getResult(CatSimStat.FIERY_CLAWS_DPS).getMax() > 0) {
				tmp.append(" Fiery Claws:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.FIERY_CLAWS_DPS).getMean())).append('\n');
				tmp.append("  Nr. ticks: ").append(round(tr.getResult(CatSimStat.FIERY_CLAWS_TICKS).getMean())).append('\n');
				if (tr.getResult(CatSimStat.FIERY_CLAWS_ROLLED).getMax() > 0) {
					tmp.append("  Nr. rolled: ").append(round(tr.getResult(CatSimStat.FIERY_CLAWS_ROLLED).getMean())).append('\n');
					tmp.append("   Rolled damage: ").append(round(tr.getResult(CatSimStat.FIERY_CLAWS_ROLLED_DMG).getMean())).append('\n');
				}
				if (tr.getResult(CatSimStat.FIERY_CLAWS_MUNCHED).getMax() > 0) {
					tmp.append("  Nr. munched: ").append(round(tr.getResult(CatSimStat.FIERY_CLAWS_MUNCHED).getMean())).append('\n');
					tmp.append("   Munched damage: ").append(round(tr.getResult(CatSimStat.FIERY_CLAWS_MUNCHED_DMG).getMean())).append('\n');
				}
				if (tr.getResult(CatSimStat.FIERY_CLAWS_REFRESH_LOST).getMax() > 0) {
					tmp.append("  Nr. refreshes Lost: ").append(round(tr.getResult(CatSimStat.FIERY_CLAWS_REFRESH_LOST).getMean())).append('\n');
					tmp.append("   Refresh lost damage: ").append(round(tr.getResult(CatSimStat.FIERY_CLAWS_REFRESH_LOST_DMG).getMean())).append('\n');
				}
				tmp.append("  Average damage: ").append(round(tr.getResult(CatSimStat.FIERY_CLAWS_AVG_DMG).getMean())).append('\n');
				tmp.append('\n');
			}

			// Other Damage
			if (tr.getResult(CatSimStat.OTHER_DPS).getMax() > 0) {
				tmp.append(" Other Sources:").append('\n');
				tmp.append("  DPS: ").append(round(tr.getResult(CatSimStat.OTHER_DPS).getMean())).append('\n');
				tmp.append('\n');
			}

		}

		// Trinkets/Procs
		// TODO: You know what would be cool?  If I sorted this.
		if (mProcUptimes != null && mProcUptimes.isEmpty() == false) {
			tmp.append("Trinkets/Procs:").append('\n');
			for (Map.Entry<ProcEffect, Double> entry : mProcUptimes.entrySet()) {
				ProcEffect proc = entry.getKey();
				Double uptime = entry.getValue();

				if (proc.buffDuration == 0)
					tmp.append(' ').append(proc.name).append(" procs: ").append(round(uptime / iterations)).append('\n');
				else
					tmp.append(' ').append(proc.name).append(" uptime: ").append(round(uptime / iterations * 100)).append('%').append('\n');
			}
		}

		return tmp.toString();
	}
}
